package com.gitee.cnzhangjie.excel.convert;

import com.gitee.cnzhangjie.excel.ExcelCell;
import com.gitee.cnzhangjie.excel.ExcelException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.ss.usermodel.*;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.*;
/**
 * java bean -> excel
 */
public class ExportConvertExcel extends ConvertExcel {

    private static final Logger logger = LogManager.getLogger(ExportConvertExcel.class);

    private final Workbook workbook;

    private final OutputStream outputStream;
    /**
     * 时间单元格
     */
    private final CellStyle dateCellStyle;

    public ExportConvertExcel(
            final ExcelType excelType,
            final OutputStream outputStream
    ) throws ExcelException {
        if (excelType == null) {
            throw new NullPointerException("this excelType argument must not be null");
        }
        this.workbook = WorkbookFactory.create(excelType);
        this.outputStream = outputStream;
        dateCellStyle = this.getDateCellStyle();
    }

    /**
     * 创建sheet和title，sheet不能重复
     *
     * @param clazz 集合中的类型
     */
    public <E> void createSheetAndTitle(
            final Class<E> clazz
    ) throws ExcelException {
        String sheetName = getSheetName(clazz);
        int sheetIndex = this.workbook.getSheetIndex(sheetName);
        if (sheetIndex >= 0) {
            throw new ExcelException("Sheet name already exists, please check class annotation.");
        }
        Sheet sheet = this.workbook.createSheet(sheetName);
        if (sheet == null) {
            throw new ExcelException("Sheet name not match, please check class annotation.");
        }
        LinkedHashMap<String, String> headCellMapping = this.getHeadCellMapping(clazz);
        // 创建标题
        // 行指针，从第0行开始
        int rowNumPtr = 0;
        Row titleRow = sheet.createRow(rowNumPtr);
        // 从第0列开始
        int headCellNumPtr = 0;
        for (Map.Entry<String, String> entry : headCellMapping.entrySet()) {
            Cell cell = titleRow.createCell(headCellNumPtr);
            cell.setCellValue(entry.getKey());
            headCellNumPtr++;
        }
        logger.debug("Create header successfully.");
    }

    /**
     * 批量添加行
     *
     * @param data 集合数据
     */
    public <E> void addRows(
            final Collection<E> data
    ) throws ExcelException {
        if (data == null || data.isEmpty()) {
            return;
        }
        Class<?> aClass = data.iterator().next().getClass();
        Sheet sheet = this.workbook.getSheet(getSheetName(aClass));
        if (sheet == null) {
            throw new ExcelException("Sheet name not match, please check class annotation.");
        }
        LinkedHashMap<String, String> headCellMapping = this.getHeadCellMapping(aClass);
        // 写数据
        // 从标题的下一行开始
        for (E datum : data) {
            this.addRow(sheet, headCellMapping, datum);
        }
    }

    /**
     * 添加一行
     *
     * @param data 集合数据
     */
    public <E> void addRow(
            final E data
    ) throws ExcelException {
        if (data == null) {
            return;
        }
        Class<?> aClass = data.getClass();
        Sheet sheet = this.workbook.getSheet(getSheetName(aClass));
        if (sheet == null) {
            throw new ExcelException("Sheet name not match, please check class annotation.");
        }
        LinkedHashMap<String, String> headCellMapping = this.getHeadCellMapping(aClass);
        // 行指针，从第0行开始
        this.addRow(sheet, headCellMapping, data);
    }

    /**
     * 获得标题和属性的映射
     * key为标题，value为实体属性名
     */
    private <E> LinkedHashMap<String, String> getHeadCellMapping(Class<E> clazz) {
        final class _ExcelCellInfo {
            private final String name;
            private final Integer exportOrder;
            private final String fieldName;

            private _ExcelCellInfo(final String name, final Integer exportOrder, final String fieldName) {
                this.name = name;
                this.exportOrder = exportOrder;
                this.fieldName = fieldName;
            }
        }
        Field[] fields = clazz.getDeclaredFields();
        List<_ExcelCellInfo> list = new ArrayList<>(fields.length);
        // 过滤
        for (Field field : fields) {
            ExcelCell excelCell = field.getAnnotation(ExcelCell.class);
            if (excelCell != null) {
                list.add(new _ExcelCellInfo(excelCell.value(), excelCell.exportOrder(), field.getName()));
            }
        }
        list.sort((o1, o2) -> {
            int exportOrderCompare = o1.exportOrder.compareTo(o2.exportOrder);
            // 如果order一样，则比较名称
            if (exportOrderCompare == 0) {
                return o1.name.compareTo(o2.name);
            }
            return exportOrderCompare;
        });
        LinkedHashMap<String, String> map = new LinkedHashMap<>(list.size());
        for (_ExcelCellInfo item : list) {
            map.put(item.name, item.fieldName);
        }
        return map;
    }

    /**
     * 创建一行
     *
     * @param sheet           当前sheet
     * @param headCellMapping 头行
     * @param data            数据
     */
    private <E> void addRow(
            final Sheet sheet,
            final LinkedHashMap<String, String> headCellMapping,
            final E data
    ) throws ExcelException {
        int rowNumPtr = sheet.getLastRowNum() + 1;
        Row row = sheet.createRow(rowNumPtr);
        int dataCellNumPtr = 0;
        Class<?> aClass = data.getClass();
        for (Map.Entry<String, String> entry : headCellMapping.entrySet()) {
            Field f;
            try {
                f = aClass.getDeclaredField(entry.getValue());
            } catch (NoSuchFieldException e) {
                throw new ExcelException("excel create row fail.msg:" + e.getMessage());
            }
            f.setAccessible(true);
            Cell cell = row.createCell(dataCellNumPtr);
            try {
                setCellValue(cell, f.get(data));
            } catch (IllegalAccessException e) {
                throw new ExcelException("excel create row fail.msg:" + e.getMessage());
            }

            dataCellNumPtr++;
        }
        logger.debug("The data with row number " + rowNumPtr + " was created successfully.");
    }

    /**
     * 根据类字段类型为单元格赋值
     *
     * @param cell       单元格
     * @param fieldValue 该单元格的字段值
     */
    private void setCellValue(
            final Cell cell,
            final Object fieldValue
    ) {
        CellType.parse(cell, fieldValue, this.dateCellStyle);
    }

    /**
     * 创建时间风格的单元格
     */
    private CellStyle getDateCellStyle() {
        CellStyle dateCellStyle = this.workbook.createCellStyle();
        dateCellStyle.setDataFormat(this.workbook.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));
        return dateCellStyle;
    }

    /**
     * 写入到文件中。
     * 注意，只能write一次。
     */
    public void write() throws ExcelException {
        try {
            workbook.write(outputStream);
        } catch (IOException e) {
            throw new ExcelException("workbook write failed, msg:" + e.getMessage());
        }
    }

    @Override
    public void close() throws ExcelException {
        // 关闭excel
        try {
            this.outputStream.flush();
            this.outputStream.close();
            this.workbook.close();
            logger.debug("Excel closed.");
        } catch (IOException e) {
            throw new ExcelException("Excel close error, msg:" + e.getMessage());
        }
    }
}
